Repository navigation
Release 1.8.0: staff area with cataloguing, circulation and plugin content, offline catalogue and a loans calendar - #47
Conversation
CameraX 1.4.1 (camera2, lifecycle, view) drives the preview and frame analysis; ZXing core 3.5.3 decodes EAN-13 in pure Java, so scanning needs no Google Play services.
…calendar Mobile API 1.6.0 support without any UI change yet: the staff_catalog, catalog_sync and calendar_feed health flags (off on older servers); the user role kept in SessionStore from login and GET /me, with StaffAccess deciding who sees the staff area; StaffApi and StaffRepository for every /staff route (a 403 drops the stored role); tolerant models for lookups, ISBN lookups, books, authors, publishers, genres and shelves; 409 duplicate and conflict context kept in ApiResult.Failure.meta. Pure, unit-tested logic: IsbnCodec (EAN-13 and ISBN-10 checksums, ISBN-10 to 13), ScanDebouncer (two identical reads, 3 s suppression, pause), StaffBookForm with StaffBookWrite (create body, true patch for edits that re-sends preserved contributors), SyncMerge (last change wins, full resync, weekly forced pass) and CalendarLinks. Room schema v2 replaces the 40-row snapshot with the whole catalogue plus cached loans, reservations, wishlist and sync bookkeeping. CatalogRepository.syncCatalog() pages through GET /catalog/sync, applies the result in one transaction and only then stores the watermark; servers without the endpoint keep the old snapshot. Loans and wishlist are written through on every fetch and purged on sign-out or a new sign-in. The sync worker refreshes the personal lists too, the cache database is excluded from backups and the foreground observer also refreshes the role.
Search falls back to the cached catalogue (title, author, publisher and identifier match, the same facets by value, offset paging, title or newest order) under an offline banner. A book page is built from the cache with a notice; Library and Wishlist show the last lists the server sent. Availability cannot be guaranteed offline, so loan and reservation affordances are hidden, never disabled: no request or reserve button, no loan calendar, no cancel actions, no wishlist toggle or reviews. Cached availability is never shown as current: the book page says when it was last known (sync time), status pills are hidden on cached cards and the cached Home shelf is labelled as recently added instead of available now. OfflinePolicy holds the decision and is unit-tested.
Profile shows "Loans in your calendar" when the server advertises calendar_feed and loans are on. The sheet creates the private ICS link, leads with Copy link (Google Calendar on Android cannot subscribe from the device; the hint explains the website route), opens it as webcal:// with the HTTPS link as fallback, and offers Regenerate and Turn off, both behind a confirmation because existing subscriptions stop updating. Profile also shows "Staff area" for admin and staff users on servers with staff_catalog, and GET /me now refreshes the stored role.
Staff hub (Profile, or the scan button on the Catalog tab) for admin and staff users on servers with staff_catalog. Books: searchable list of every record with edit and delete (refused with a clear message while loans or reservations are active). One create/edit form serves scanned titles and records without an ISBN: live identifier validation, author and publisher pickers with add-new, genre and subgenre, media type, language/format/acquisition suggestions from the lookups, shelf and level with the next free position, copies (create) or add copies (edit), and a cover from a URL, a photo or a picked image (resized, EXIF-rotated JPEG under 2 MB, uploaded after the save). A 409 duplicate offers Open existing, Add copy or Change identifier. Authors, publishers, genres (tree, find-or-create) and shelves with levels have their own screens. Scanner: CameraX preview with a viewfinder, ZXing EAN-13 decoding on alternate rotated frames, checksum validation and the double-read debouncer. Batch mode remembers copies, shelf, level and an opt-in auto-confirm; preview, duplicate and not-found sheets lead to add, add copies, edit details or create manually. The manual ISBN/EAN field is always visible for emulators and denied permission. CAMERA is requested only here, with a rationale and a settings fallback when it is permanently denied; camera.any is optional so devices without a camera still install the app. Without a connection the scanner says that scanning needs one.
…oans calendar Bump the version and record the new features, their server requirement (Mobile API 1.6.0) and what is not yet verified on a device in STATUS.md and the Uwe parity notes.
The feed URL carries a bearer-equivalent token. Copy link now flags the clip as sensitive (ClipDescription.EXTRA_IS_SENSITIVE on Android 13+, the same extra key before) with a neutral label, so the clipboard preview and keyboards do not show or keep it, and the sheet shows only the host and the last six characters instead of the full URL. The URL is never logged.
Blank fields became null and the shared Json drops null keys, so the server kept the old value and a pseudonym, date, website or GND id could never be emptied. Every editable field is now always sent, trimmed, with a cleared field as an empty string, which the server stores as empty (dates and the GND id as NULL). RecordWritesTest checks the encoded bodies.
A search or pull-to-refresh during a page load dropped that page through the generation check but left loadingMore set, so loadMore() never ran again. reload() now clears the flag.
Picking a shelf without a level sent null shelf and level ids, which cleared the stored location without a word. A shelf without a level is now a validation error with a message under the level field (all four languages); nulls are sent only when the shelf is set to none, and a stored location without a level is left untouched instead of making the form invalid. When the record saved but the cover upload failed, the screen navigated away and the error was lost. It now stays on the saved record (switching a create into an edit of the new id), keeps the pending cover so the next Save retries it, and says so.
Search: rows store a folded search key (NFD, combining marks removed, lower case; identifiers without dashes or spaces) and queries are folded the same way, so Città, Über or ÆRØ match regardless of accents and case. LIKE metacharacters in the query and the author/publisher filters are escaped with ESCAPE, and identifier-like queries drop their separators. Room schema v3 (destructive, refilled by the next full sync). Sync: a page with has_more and no cursor now fails the run instead of applying a partial result and advancing the watermark. Clearing the cache (forget instance) bumps a generation under a write lock that every sync write also takes, so a run in flight can no longer write the old library's rows or watermark after the clear. The book detail ETag cache is concurrent.
Any 403 counted as a revoked role, so a WAF or proxy block wiped the stored role and closed the form. ApiResult.Failure now records whether its code came from the server's envelope, and only an envelope error.code of forbidden drops the role.
A ConnectivityObserver (system default-network callback, Hilt singleton) now feeds Home, Search, book detail, Library and Wishlist. When the device goes offline, screens already loaded online hide availability pills, the available-now claim, loan and reservation buttons, the loan calendar, cancel and remove actions at once; when it comes back they reload and the offline catalogue syncs. Unit tests cover each screen state. A NETWORK failure never reaches the UI as its IOException text: resolvedMessage() and the new uiText()/serverMessage() helpers give a localized offline message, Library and Wishlist without a saved copy show an offline empty state, Home shows the same wording, staff screens map their load errors likewise, and the desiderata, archive and article sections of Catalog search are hidden when they fail only for lack of a connection. The author, publisher and genre pickers and lists get their own search placeholders.
Counts read "Copies: 1" style in every language instead of a plural noun after the number ("1 copies"): batch copies, added this session, copies added, available of total and the offline last-known line. The calendar sheet now says the feed carries loans (due dates, books ready for pickup, booked loans), not reservations.
The header kept the role of the profile loaded when the screen opened, so a downgraded admin still read Admin while the staff entries had already gone. It now shows the stored role, refreshed by every GET /me, and Profile re-reads GET /me quietly when it comes back to the foreground.
A staff create runs the whole admin pipeline (scraper hooks, cover download) and took over 30 s on a loaded server during the live smoke test: the app timed out and reported a failure for a record the server had already saved. The read timeout is now 60 s; the connect timeout is unchanged, so an unreachable server is still detected quickly.
The new staffe2e suite drives the real app (MainActivity, real Hilt graph, ViewModels, repositories and Room) against FakePinakes, a MockWebServer on the device loopback that answers JSON fixtures from androidTest/assets/fixtures and records every request. UiAutomator handles the system camera prompt; airplane mode is real. Covered: staff entry only for admin/staff with staff_catalog (hidden for standard users, without the flag and after a server-side downgrade, header included); scanner manual ISBN to preview to add with the counter, duplicate to add copies, not found to the manual form, invalid code rejected without a lookup, camera denied keeps manual entry; manual form with a missing title or a shelf without a level sends nothing, an edit sends only the changed key; offline search banner with cached results and no availability or loan actions, offline detail with the last-known wording and no loan buttons, Library and Wishlist offline empty state without raw exception text, going offline after an online load hides the loan button and reconnecting restores it; calendar link shown masked, copied as sensitive, regenerate and turn off both confirm. 18 tests, all green on Android 15 together with the 22 existing ones.
tools/e2e/live-smoke.sh drives the installed app on an emulator with adb and uiautomator (tools/e2e/ui.py) like a user: onboarding, sign-in, staff hub, scan-add of an ISBN with two copies, a copy through the duplicate sheet, a record without ISBN, its shelf and level on edit (checking that nothing else changed), airplane-mode search and detail, the calendar link and its feed. Every step is checked through the Mobile API, and cleanup removes only what the run created (books, a calendar link that did not exist before, device tokens issued during the run) and clears the app. Instance URL and credentials come only from PINAKES_E2E_* variables; texts follow the device language through i18n/*.json. STATUS.md documents how to run it and the device tests.
The administrator can now hide the gender and tax code fields. The app reads it from GET /me (hidden_fields, the keys themselves omitted), from the registration schema (profile_fields.*.visible) and from /health (registration.show_*): a hidden field is not rendered in the profile editor and never sent in PATCH /me. Servers that send none of these keep both fields, as before. Registration never asked for them and still does not. Profile re-reads the schema with /me when it comes back to the foreground. Tests: ProfileFieldVisibilityTest for the decision and the payloads; ProfileFieldsFlowTest on the device for hidden and shown fields, registration, and the birth-date picker fitting a 360dp screen.
Staff Authors and Publishers rows now open a detail page: a padded info card (books in the catalogue, life dates, nationality, website, GND id or the publisher's contacts, and created/updated when the server sends them) and the record's books from GET /staff/books?author_id= (publishers by name), drawn with the whole-cover book cards. Editing moved to the page's edit action. The catalogue filtered by an author gets the same kind of card above the results. The pending cover preview in the book form and the covers in My reviews now fit instead of cropping.
The loan request used Material's DatePickerDialog, which is a fixed 360dp wide with a capped height, and day cells were a fixed 40dp: on 320-360dp phones the month was clipped and cells ran into each other. The request is now a plain dialog sized to the screen (16dp margins, at most 420dp, scrollable), day cells fill their column (at most 44dp), weekday headers share the grid's spacing, and the legend wraps. When availability cannot be loaded the same grid lets the user pick any future day instead of a second date picker. CalendarLayoutTest checks the 7 weekday columns, that every day cell stays inside the screen and inside a 320dp column, and month navigation.
With database access configured, the smoke test hides gender and tax code in the settings table, waits for /health to report it, checks that /me omits them and that the app's profile editor no longer shows them, then restores the previous values. STATUS.md lists the new device tests.
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configuration
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Important
Two issues should be fixed before release. The author and publisher editor can wipe a record after a failed load. The search screen gains a new connectivity collector every time a search is submitted, the sort changes or filters are cleared. The other inline notes are smaller.
Reviewed changes
This review covers the whole 1.7.0 release branch: the data layer, staff area, scanner, offline catalogue, calendar and the screen changes. Test fixtures were skimmed.
- Staff area: adds role-gated catalogue management (books, authors, publishers, genres, shelves). Book edits send patch bodies, and a role is revoked only on the server's own
forbiddencode. - ISBN scanner: adds CameraX and ZXing EAN-13 scanning. A code needs two identical reads, then a 3 s suppression window applies. Includes batch mode and preview, duplicate and not-found sheets.
- Offline catalogue: moves Room to a v3 schema. The delta sync on
/catalog/syncuses a guarded watermark, falls back to the legacy snapshot on servers without the endpoint, and serves offline search with a folded search key. Loans and wishlist are cached too, and loan actions are hidden offline throughConnectivityObserver. - Loans calendar: adds create, rotate and revoke for the private ICS feed. The link is shown masked and copied as a sensitive clip.
- Misc: hides profile fields the library hides, raises the read timeout to 60 s, bumps the version to 20 / 1.7.0, and adds device tests and the live smoke script.
The sync path holds up. The watermark is stored only after a successful apply, cacheGeneration stops a run still in flight when the instance is forgotten from writing back, and LIKE input is escaped. A few lower-confidence leftovers are not flagged inline: cached_books.isWanted is per-user and survives logout, and the cached wishlist and loans only refresh on a full list fetch, not after add or remove.
claude-opus-5-5 | 𝕏
When GET /staff/authors/{id} or /staff/publishers/{id} failed, the editor kept a blank placeholder with Save enabled; since saves send every field and an empty string clears it, saving wiped the stored record. The placeholder is now marked as not loaded: it shows the error with Retry only (no fields, no Save), edits and submit ignore it, and a late answer for an editor already closed is dropped.
submitSearch(), setSort() and clearFilters() each started another connectivity collector that was never cancelled, so one reconnect re-ran the search once per earlier interaction. Only the collector started in init remains. ConnectivityCollectorTest checks that a single collector is left after repeated searches, sorts and filter resets.
Creating, regenerating or turning off the calendar link, saving the profile and changing the password showed the IOException text when offline. They now use the server's own message or the localized offline wording.
"Add copy" sent the form's copy count, which in edit mode is the edited record's own total: an ISBN change that collided with another book added that many copies to it. Editing now adds exactly one; creating keeps the copies the user asked for.
The 3 s window started when a code was accepted, but the lookup and the open sheet usually last longer, so after Skip or Add the book still under the camera reopened the same sheet. resume(now) restarts the window for the last accepted code; other codes are not held back.
There was a problem hiding this comment.
Important
The new Tier 2 screens have two behaviour bugs. Fix them before the server turns these flags on. Scanning an issue with the camera can't identify the issue, because the barcode add-on is dropped. Saving a book club workflow also strips any state flag the app doesn't model. A stale STATUS.md note is inline too.
Reviewed changes
This review covers the one commit added since the last pullfrog review at 80d7173.
- Periodicals desk: added a scanner and manual code entry that look up an issue (
scan-lookup), then receive or claim it depending on its status. Below the scanner is a searchable, paged list of mastheads. Each masthead page has expandable years with their issues, plus forms to add an issue or a year and to create or edit a subscription. - Book clubs: added a club list with a create sheet. Each club page has settings, a workflow editor (at least two states, which can be reordered, added or removed, each with voting/current/archived flags), member role and status chips, adding a member by user search, and delete.
- FRBR works: added a work search (up to 100 results) and a create sheet. Each work page has an edit form, add/edit/delete for expressions, and linking or unlinking books.
- Shared scanner and plumbing: moved the copy scanner's camera viewfinder into
ScannerBox. AddedStaffTier2Api/StaffTier2Repository(every call goes throughstaffCall), the pureTier2Forms, and new routes and hub rows, each gated on its own area.StaffAreas.anyPluginnow covers the three new areas. - Tests and i18n: added
Tier2FormsTest(7 tests),Tier2FlowTest(6 tests) and their fixtures, plus about 135 keys in each of the four languages. All four files have the same keys and matching placeholders, and none of them contain an em dash.
claude-opus-5-5 | 𝕏
The live smoke looks up an unknown code at the periodicals desk (read-only: the bridge offers no deletes), and creates a book club and an FRBR work in the app, removing them in cleanup; long forms scroll to their Save button. STATUS and the Uwe parity notes now describe the periodicals desk, book club management and FRBR works as native, with the 19/19 live run.
There was a problem hiding this comment.
✅ No new issues in this commit, and it fixes the stale
STATUS.mdnote from the last review. Two findings from the review atb8db4c6are still open because this commit changes no app code: the camera path drops the periodical's EAN add-on, and workflow saves strip state flags the app doesn't model.STATUS.mdnow says an arriving issue can be scanned, but with the camera that only works after the add-on fix.
Reviewed changes
This review covers the one commit added since the last pullfrog review at b8db4c6. It changes only docs and the live smoke script.
STATUS.mdand parity notes: rewrote the Tier 2 entry to describe the periodicals desk, book clubs and FRBR works as shipped, each behind its own flag, and updated the Italian 1.8.0 summary indocs/UWE-PARITY.mdto match. The new counts (377 unit tests, 92 device tests) match the@Testtotals inapp/src/testandapp/src/androidTest.- Live smoke Tier 2 steps: added
periodicals_desk, which first checks through the API that a fixed code has no match and then looks it up in the app without changing anything. Also addedclub_createandwork_create, which create a record in the app and confirm it through the API. Each step is skipped while its flag is off. EveryS[...]key they use exists ini18n/en.json, and the API paths matchStaffTier2Api. - Cleanup: clubs and works are looked up again by their run-unique names, so a step that fails partway still gets swept. Each one is then deleted by id.
claude-opus-5-5 | 𝕏
Server labels and error messages followed the installation's language, so an English app showed "Prestato" next to English text. The interceptor now adds Accept-Language with the language picked in Profile (or the device one) unless the request sets it; Mobile API 1.7.0 answers in it when the installation has that language and keeps its own otherwise.
At 360 dp the Reservations tab wrapped mid-word ("Reservation" / "s"), and German and Italian labels are just as long. The tabs are now a scrollable row with single-line labels, like the circulation desk.
Adds 24 screenshots of the 1.8.0 staff area (circulation desk, cataloguing and plugin content), captured on Android 15 at 360 dp against a local instance with demo data and resized to 720 px, and lists the staff area, circulation desk, offline catalogue and loans calendar in the features table.
There was a problem hiding this comment.
✅ No new issues in the two new commits. Two findings from the review at
b8db4c6are still open, because these commits don't touch that code: the camera path drops the periodical's EAN add-on, and saving a workflow strips state flags the app doesn't model.
Reviewed changes
This review covers the two commits added since the last pullfrog review at 9be8401.
- App language on every request:
AuthInterceptornow addsAccept-LanguagefromAppLocale.current()before it handles the no-auth marker. Public calls and the long-timeout client get the header too, since that client is derived from the sameokHttpClient. A header set on the request itself wins, and nothing is sent for a blank orundtag.AcceptLanguageTestcovers all three cases. - My Library tabs: replaced
TabRowwithScrollableTabRow(edgePadding = 0.dp)and made the labels single-line, so "Reservations" / "Vormerkungen" no longer wrap mid-word. The tabs now sit at their natural width, starting from the left, instead of filling the row evenly.
One low-confidence note, not raised inline because it depends on the server: the HTTP cache is keyed by URL only and isn't cleared when the language changes. If the server's ETag stays the same across languages, a 304 after a language switch would replay the response in the old language until the data changes.
claude-opus-5-5 | 𝕏
The profile entry and the hub title read "Area staff" / "Staff area"; they now say "Area admin" (Admin area, Adminbereich, Espace admin), and the README follows.
The book page is its own destination above the tabs, so the Home/Catalogue/Library/Wishlist/Profile bar vanished there. The selected tab now lives in the nav host: the book page shows the same bar with the tab it was opened from, and a tap returns to that tab.
There was a problem hiding this comment.
ℹ️ No critical issues in the new commit. One minor note is inline: the remembered tab now survives a logout. Two findings from the review at
b8db4c6are still open, because this commit doesn't touch that code.
Reviewed changes
This review covers the one commit added since the last pullfrog review at d8f9b3a.
- Bottom navigation on the book page: moved the selected tab out of
MainScaffoldand intoPinakesNavHostasmainTab, soBookDetailScreencan show the samePinakesBottomBarwith the tab it was opened from. Tapping a tab setsmainTaband pops back toMAIN_GRAPH. When the active tab is hidden,MainScaffoldnow falls back to Home through a derivedshownTabinstead of writing to local state.
claude-opus-5-5 | 𝕏
The copy code, ISBN and periodical issue fields kept their text in the view model's state flow, so a keyboard-wedge reader (or a fast typist) that sends the whole label at once lost characters: LIB-239 arrived as L and the desk answered that no copy has that code. The text now stays in the screen and the view model only receives it on search.
There was a problem hiding this comment.
✅ No new issues found. Three findings from earlier reviews are still open, because this commit doesn't touch that code. Two are from the review at
b8db4c6: the camera drops the periodical's EAN add-on, and workflow saves strip state flags. The third is from8722971:mainTabsurvives a logout.
Reviewed changes
This review covers the one commit added since the last pullfrog review at 8722971.
- Scan code fields keep every character: the ISBN scanner, copy scan and periodicals desk used to keep the manual field's text in the view model's
StateFlow. Each field now keeps its own text in a screen-localrememberSaveable, so a keyboard-wedge reader that types a whole label at once no longer loses characters on the per-keystroke round trip.submitManual(input)now returns whether the code was taken, and the field is cleared only in that case. That keeps the old behaviour of leaving the text in place while a lookup or sheet is open. The length caps stay in the screen (CopyCodeCodec.MAX_LENGTH, 30 for issues).PinakesTextFieldis single-line by default, so the reader's trailing Enter still triggers theSearchaction. No test or caller still refers to the removedmanualInput/onManualInput/onManualmembers.
claude-opus-5-5 | 𝕏
A reader who has not borrowed the title saw the reviews section with no way to write one and no reason why. Below the reviews the page now says the book can be reviewed once it has been borrowed, in the four app languages.
There was a problem hiding this comment.
✅ No new issues found. Three findings from earlier reviews are still open, because this commit doesn't touch that code. Two are from the review at
b8db4c6: the camera drops the periodical's EAN add-on, and saving a workflow strips state flags. The third is from8722971:mainTabsurvives a logout.
Reviewed changes
This review covers the one commit added since the last pullfrog review at 1b967c4.
- Hint for readers who can't review yet:
ReviewsBodynow showsreviews_borrow_firstwhen the editor is closed, the reader has no review yet, and neither the server'scan_reviewnor thehasReadfallback allows a review. Before, no button appeared and nothing explained why. The key is in all four languages and none of them contain an em dash. The text carries areviews-borrow-firsttest tag, but no device test uses it yet.
claude-opus-5-5 | 𝕏
The selected tab moved to the nav host for the book page's bottom bar, so it outlived MAIN_GRAPH: logging out from Profile and back in (another account, another library) reopened on Profile. Logout and any non-authenticated state now reset it to Home.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
This run covers the one commit pushed since the last pullfrog review at 60c5e4a.
- New sessions open on Home:
onLoggedOutnow resetsmainTabtoPinakesTab.Home. A newLaunchedEffect(authState)also resets it for any state other thanAuthenticated, which covers an expired or revoked token and a forgotten instance.SessionStoreseedsauthStatesynchronously from prefs, so the effect does not clear a tab thatrememberSaveablerestores on a cold start while the user is signed in. This addresses themainTablogout thread raised at8722971.
claude-opus-5-5 | 𝕏
ZXing reports an EAN's 2/5-digit add-on only as result metadata, and the analyzer passed on the 13 digits alone, so a camera scan could match the masthead but never the issue. The periodicals desk now asks for the add-on to be appended (the issue code scan-lookup expects); the book scanner keeps ignoring it, since on an ISBN the 5-digit add-on is a price. EanAddOnDecodeTest decodes a real masthead + add-on label from its luminance plane both ways.
The draft kept only voting, current and archived and sent only those three, so renaming a label stripped every other flag the server stored (votable on proposed, or any flag a later plugin version adds). Each state now carries its original flags object and the editor overwrites only the three it controls.
There was a problem hiding this comment.
ℹ️ No critical issues. Both remaining
b8db4c6findings are fixed. One minor suggestion is inline.
Reviewed changes
This run covers the two commits pushed since the last pullfrog review at e52bdfb.
- Camera scans keep the issue add-on:
BarcodeAnalyzertakesappendEanAddOn, andcodeOfappends ZXing'sUPC_EAN_EXTENSIONmetadata to the 13 digits. The flag goes throughCameraPreviewandScannerBox, and only the periodicals desk turns it on. The book scanner and copy scan still read just the 13 digits.EanAddOnDecodeTestdecodes a real luminance-plane fixture and gets both results. - Workflow saves keep unmodelled flags:
WorkflowStateDraft.serverFlagsstores the state's originalflagsobject.toJsonwrites those flags first and then setsvoting/current/archivedon top, so flags likevotablesurvive a save.Tier2FormsTestcovers both cases: the stored flags are kept, and the editor's own flags override them.
claude-opus-5-5 | 𝕏
…a masthead ZXing reads the add-on only on some frames, so two bare 13-digit reads could confirm the masthead before the read with the issue won. On the desk a confirmed 977 masthead is now held for 800 ms: a read with its add-on is taken at once, a bare read right after an add-on read counts as that issue, and a masthead with no add-on is still looked up when the hold ends.
There was a problem hiding this comment.
ℹ️ No critical issues. The add-on finding is fixed. One minor suggestion is inline.
Reviewed changes
This run covers the one commit pushed since the last pullfrog review at 07e4313.
- The periodicals desk waits for the issue add-on: the new pure
IssueScanResolverwrapsScanDebouncer. A bare977…read that comes right after a read with its add-on counts as that issue. A confirmed bare masthead is held for 800 ms, and a read with its add-on during that time is taken at once. A masthead with no add-on is still looked up whenflushruns after the hold. The desk VM runs a singleholdJobfor the hold, andlookupcancels it.IssueScanResolverTestcovers alternating frames, the held masthead, a book EAN that is never held, and the paused state.
claude-opus-5-5 | 𝕏
…a denied camera 1.8.0-rc.1 shipped as versionCode 21, so the stable build needs 22 to install over it as an update. The camera-denied scanner test now revokes the permission itself instead of depending on what the emulator was left with.
There was a problem hiding this comment.
ℹ️ No critical issues. There are two minor suggestions inline: one test fix and one stale doc line.
Reviewed changes
This run covers the one commit pushed since the last pullfrog review at 4da43cc.
- Bumped
versionCodeto 22: the stable 1.8.0 build now installs over the 1.8.0-rc.1 build, which used 21. - Camera-denied scanner test revokes the camera first:
cameraDeniedKeepsManualEntryAndExplainsWhycallsUiAutomation.revokeRuntimePermission(…, CAMERA)beforelaunch().
The open IssueScanResolver.kt:39 thread from the previous review is not touched by this commit, so it stays open.
claude-opus-5-5 | 𝕏
…r the camera The held-masthead path returned the add-on read without asking the debouncer, so after receive, claim or dismiss two bare frames plus one add-on frame reopened the same issue. The resolver now drops any read the debouncer still suppresses, or that is the bare masthead of it.
Revoking a granted permission from inside the test kills the instrumentation process, which shares the app's UID, and the rest of the suite with it. The test now skips with the adb command that resets the permission, the README run steps include it, and STATUS.md names versionCode 22.
There was a problem hiding this comment.
Important
The new suppression check in IssueScanResolver breaks camera lookups for magazines that have no add-on. Details are inline.
Reviewed changes
This run covers the two commits pushed since the last pullfrog review at ea339a9.
- The desk no longer reopens the issue it just handled:
IssueScanResolver.offerdrops any read thatScanDebouncer.suppressedAt(now)(new) starts with, so neither the issue nor its bare masthead is looked up again while the magazine stays under the camera.IssueScanResolverTest.theIssueJustHandledIsNotLookedUpAgainAfterResumecovers it. - The camera-denied test skips instead of revoking:
cameraDeniedKeepsManualEntryAndExplainsWhynow usesassumeTrueto skip when CAMERA is already granted, and the README lists theadb shell pm revokecommand to run first. This fixes the process-kill finding. - Updated
STATUS.mdto versionCode 22.
claude-opus-5-5 | 𝕏
The held masthead is the debouncer's last accepted code, so the post-resume check matched its own next frame and cleared the hold: a magazine with no add-on was never looked up from the camera while it stayed in view. The check now skips the held masthead, and the no-add-on test feeds frames during the hold.
There was a problem hiding this comment.
✅ No new issues found. The resolver fix restores camera lookups for magazines without an add-on.
Reviewed changes
This run covers the one commit pushed since the last pullfrog review at e4ce70e.
- Kept a held masthead alive while its own frames keep arriving:
IssueScanResolver.offernow skips the suppression early return whensuppressed == pending. The magazine's next bare frames fall through toScanDebouncer, which ignores them without clearing the hold, soflushstill looks up a title-only masthead. Post-resume suppression is unchanged becausependingis null after a lookup.aMastheadWithNoAddOnIsStillLookedUpAfterTheHoldnow feeds frames during the hold and assertsholding.
claude-opus-5-5 | 𝕏

What this adds
Pinakes Android 1.8.0 (versionCode 22) brings a native admin area for admin and staff accounts, an offline catalogue and a subscribable loans calendar. Each staff section appears only when the signed-in role is admin or staff and the server advertises it (Mobile API 1.7.0, fabiodalez-dev/Pinakes#477); on older servers the entries stay hidden and the app behaves as before.
Staff area
Idempotency-Key, so a retry never lends a second copy. The whole section is hidden offline.Offline catalogue
Room mirror of the whole catalogue kept current by
/catalog/sync, plus the user's loans, reservations and wishlist. Search, book pages, My Library and Wishlist work offline; loan and reservation actions are hidden (not disabled) while offline and cached availability is shown as "last known".Loans calendar
Profile → "Loans in your calendar": a private link with loans (start to due date) and reservations, copied as a sensitive clip and shown masked, with webcal opening, regenerate and turn off.
Other
Accept-Language), so server labels and messages follow it; dates follow the language picked in the app.Tests
tools/e2e/live-smoke.shdrives the real app against a real Pinakes instance (19 steps, cleanup of everything it creates);STATUS.mdexplains how to run both suites.Fixes #39
Fixes #32
Fixes #33